/*
 * This file is part of ELCube.
 *
 * ELCube is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ELCube is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with ELCube.  If not, see <https://www.gnu.org/licenses/>.
 */
package cn.nkpro.elcube.task.impl;

import cn.nkpro.elcube.data.elasticearch.SearchEngine;
import cn.nkpro.elcube.docengine.model.DocHV;
import cn.nkpro.elcube.docengine.service.NkDocEngineFrontService;
import cn.nkpro.elcube.exception.NkDefineException;
import cn.nkpro.elcube.security.SecurityUtilz;
import cn.nkpro.elcube.task.model.BpmTaskComplete;
import cn.nkpro.elcube.task.model.BpmTaskES;
import cn.nkpro.elcube.task.model.BpmTaskTransition;
import cn.nkpro.elcube.utils.BeanUtilz;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.history.HistoricTaskInstance;
import org.camunda.bpm.engine.history.HistoricVariableInstance;
import org.camunda.bpm.engine.impl.pvm.PvmActivity;
import org.camunda.bpm.engine.impl.pvm.PvmScope;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.task.DelegationState;
import org.camunda.bpm.engine.task.IdentityLink;
import org.camunda.bpm.engine.task.Task;
import org.camunda.bpm.engine.task.TaskQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.Assert;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 工作流任务的一些公共方法
 */
public abstract class AbstractNkBpmSupport {

    @Autowired@Lazy
    protected NkDocEngineFrontService docEngine;
    @Autowired@Lazy
    protected ProcessEngine processEngine;
    @Autowired@Lazy
    protected SearchEngine searchEngine;

    protected List<String> assignee(String taskId,String assignee){
        if(StringUtils.isNotBlank(assignee)){
            return Collections.singletonList(assignee);
        }
        return processEngine.getTaskService().getIdentityLinksForTask(taskId)
                .stream()
                .filter(identityLink -> StringUtils.equals(identityLink.getType(),"candidate"))
                .map(IdentityLink::getUserId)
                .collect(Collectors.toList());
    }

    void indexDocTask(HistoricTaskInstance taskInstance, DocHV doc){
        Map<String, Object> variables = getHistoricVariables(taskInstance.getProcessInstanceId());

        String state;
        Long startTime  = taskInstance.getStartTime().getTime()/1000;
        Long endTime;
        List<String> assignee = Collections.emptyList();
        if(taskInstance.getEndTime()==null){
            state   = "create";
            endTime = null;
            assignee = assignee(taskInstance.getId(), taskInstance.getAssignee());
        }else{
            state   = variables.containsKey("NK$DELETE")?"delete":"complete";// 任务被强制删除
            endTime = taskInstance.getEndTime().getTime()/1000;
            if(StringUtils.isNotBlank(taskInstance.getAssignee())){
                assignee = Collections.singletonList(taskInstance.getAssignee());
            }
        }

        if(doc==null){
            doc = docEngine.detail((String) variables.get("NK$BUSINESS_KEY"));
        }
        BpmTaskES bpmTaskES = BpmTaskES.from(doc,
                taskInstance.getId(),
                taskInstance.getName(),
                assignee,
                state,
                startTime,
                endTime
        );
        searchEngine.indexBeforeCommit(bpmTaskES);
    }

    List<HistoricVariableInstance> getHistoricVariableInstances(String processInstanceId){
        return processEngine.getHistoryService()
                .createHistoricVariableInstanceQuery()
                .processInstanceId(processInstanceId)
                .list();
    }

    private Map<String, Object> getHistoricVariables(String processInstanceId){
        // 获取所有流程变量
        List<HistoricVariableInstance> variables = getHistoricVariableInstances(processInstanceId);

        return variables
                .stream()
                .filter(instance->StringUtils.equals(instance.getExecutionId(),processInstanceId))
                .collect(Collectors.toMap(HistoricVariableInstance::getName,HistoricVariableInstance::getValue));
    }

    // 获取流程图内所有的节点
    List<? extends PvmActivity> getProcessDefinitionActivities(String processDefinitionId){
        return (
                (PvmScope) processEngine.getRepositoryService()
                        .getProcessDefinition(processDefinitionId)
        ).getActivities();
    }

    // 当前任务节点的对外连接线
    List<BpmTaskTransition> getTaskTransition(List<? extends PvmActivity> activities, String taskDefinitionKey){
        // 当前任务节点的对外连接线
        PvmActivity pvmActivity = activities.stream()
                //如果是会签节点，那么 activityId 格式为： activityId#multiInstanceBody
                .filter(activity -> StringUtils.equals(activity.getId().split("#")[0],taskDefinitionKey))
                .findFirst()
                .orElseThrow(()->new NkDefineException("没有找到流程任务定义"));

        return pvmActivity.getOutgoingTransitions()
                .stream()
                .map(pvmTransition -> {
                    BpmTaskTransition transition = BeanUtilz.copyFromObject(pvmTransition,BpmTaskTransition.class);
                    transition.setName(StringUtils.firstNonBlank(
                            transition.getName(),
                            (String) pvmTransition.getProperty("name"),
                            transition.getId())
                    );
                    return transition;
                }).sorted(Comparator.comparing(BpmTaskTransition::getId)).collect(Collectors.toList());
    }



    void completeTask(BpmTaskComplete bpmTask, Task task){


        if(StringUtils.isBlank(task.getAssignee()))
            processEngine.getTaskService().claim(bpmTask.getTaskId(), SecurityUtilz.getUser().getId());

        String comment = bpmTask.getTransition().getName() + (StringUtils.isNotBlank(bpmTask.getComment())?(" | "+ bpmTask.getComment()):"");
        processEngine.getIdentityService().setAuthenticatedUserId(SecurityUtilz.getUser().getId());
        processEngine.getTaskService().createComment(bpmTask.getTaskId(),task.getProcessInstanceId(),comment);
        if(task.getDelegationState() == DelegationState.PENDING){
            processEngine.getTaskService().resolveTask(bpmTask.getTaskId(), Collections.singletonMap("NK$TRANSITION_ID",bpmTask.getTransition().getId()));
            reindex(task, task.getOwner());
        }else{
            // 提交流程表单
            if(MapUtils.isNotEmpty(bpmTask.getForm())){
                processEngine.getTaskService().setVariables(bpmTask.getTaskId(), Collections.singletonMap("NK$TRANSITION_ID",bpmTask.getTransition().getId()));
                processEngine.getFormService().submitTaskForm(bpmTask.getTaskId(),bpmTask.getForm());
            }else{
                processEngine.getTaskService().complete(bpmTask.getTaskId(), Collections.singletonMap("NK$TRANSITION_ID",bpmTask.getTransition().getId()));
            }
        }
    }

    void reindex(Task task, String assignee){
        ProcessInstance processInstance = processEngine.getRuntimeService().createProcessInstanceQuery()
                .processInstanceId(task.getProcessInstanceId())
                .singleResult();

        // 因为转办不会走全局事件更新索引，所以这里需要手动更新任务索引
        BpmTaskES bpmTaskES = BpmTaskES.from(docEngine.detail(processInstance.getBusinessKey()),
                task.getId(),
                task.getName(),
                Collections.singletonList(assignee),
                "create",// 任务被强制删除
                task.getCreateTime().getTime()/1000,
                null
        );

        searchEngine.indexBeforeCommit(bpmTaskES);
    }

    Task getTask(String taskId, String userId){
        Assert.notNull(taskId,     "任务ID不能为空");

        TaskQuery taskQuery = processEngine.getTaskService()
                .createTaskQuery()
                .taskId(taskId);

        if(StringUtils.isNotBlank(userId)){
            taskQuery.or()
                .taskCandidateUser(SecurityUtilz.getUser().getId())
                .taskAssignee(SecurityUtilz.getUser().getId())
                .endOr();
        }

        Task task = taskQuery.singleResult();

        Assert.notNull(task,     "任务不存在");
        return task;
    }
}
